package com.googlecode.gwtbb.server.service;

import com.googlecode.gwtbb.client.shared.GwtbbSmess;
import com.googlecode.gwtbb.client.shared.SharedMessages;
import com.googlecode.gwtbb.client.shared.entity.user.User;
import com.googlecode.gwtbb.client.shared.entity.user.UserBase;
import com.googlecode.gwtbb.client.shared.entity.user.UserSnap;
import com.googlecode.gwtbb.client.shared.remote.user.LoginResponse;
import com.googlecode.gwtbb.client.shared.remote.user.LoginUserBean;
import com.googlecode.gwtbb.client.shared.remote.user.RegisterResponse;
import com.googlecode.gwtbb.client.shared.remote.user.RegisterUserBean;
import com.googlecode.gwtbb.client.shared.validation.Failure;
import com.googlecode.gwtbb.client.shared.validation.FieldId;
import com.googlecode.gwtbb.client.shared.validation.GwtbbValidator;
import com.googlecode.gwtbb.server.dao.user.UserDao;
import com.googlecode.gwtbb.server.dao.user.UserSnapDao;
import com.googlecode.gwtbb.server.intermediate.ProcessNames;
import com.googlecode.gwtbb.server.intermediate.SharedMessagesProxyHandler;
import com.googlecode.gwtbb.server.intermediate.user.PrivilegeChecker;
import com.googlecode.gwtbb.util.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Proxy;
import java.util.Date;
import java.util.Map;

/**
 * @author Abraham Grief
 * @version $Id$
 * @since Jun 25, 2008
 */
public class UserService extends ClientService implements com.googlecode.gwtbb.client.only.remote.user.UserService, PrivilegeChecker {
	private static Log log = LogFactory.getLog(UserService.class);

	private UserDao userDao;
	private UserSnapDao userSnapDao;

	static {
		GwtbbSmess.sharedMessages = (SharedMessages)Proxy.newProxyInstance(
			SharedMessagesProxyHandler.class.getClassLoader(),
			new Class[]{SharedMessages.class},
			new SharedMessagesProxyHandler());
	}

	public UserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public UserSnapDao getUserSnapDao() {
		return userSnapDao;
	}

	public void setUserSnapDao(UserSnapDao userSnapDao) {
		this.userSnapDao = userSnapDao;
	}

	public User checkPrivilege(Map<FieldId, Failure> failures, Long userId, String sessionId, UserBase.PrivilegeLevel privilegeLevel) {
		User user = userDao.select(userId);
		if (user == null)
			failures.put(null, new Failure(GwtbbSmess.instance().userIdDoesNotExist()));
		else if (!getSessionId(user).equals(sessionId))
			failures.put(null, new Failure(GwtbbSmess.instance().loggedOut()));
		else if (user.getPrivilegeLevel().ordinal() < privilegeLevel.ordinal())
			failures.put(null, new Failure(GwtbbSmess.instance().insufficientPrivileges()));
		return user;
	}

	public User get(Long id) {
		if (id == null)
			return null;
		return userDao.selectSansPassword(id);
	}

	public LoginResponse login(LoginUserBean loginUserBean) {
		if (loginUserBean == null)
			return null;

		LoginResponse retVal = new LoginResponse();
		Map<FieldId, Failure> failures = loginUserBean.validate(GwtbbValidator.newMap(), 0);
		String sessionId = null;
		User user = null;
		if (failures.isEmpty()) {
			User dbUser = userDao.selectByUsername(loginUserBean.getUsername());
			if (dbUser == null)
				failures.put(FieldId.create(UserBase.USERNAME, 0), new Failure(GwtbbSmess.instance().usernameDoesNotExist()));
			else if (!dbUser.getPassword().equals(loginUserBean.getPassword()))
				failures.put(FieldId.create(UserBase.PASSWORD, 0), new Failure(GwtbbSmess.instance().invalidPassword()));
			else if (!dbUser.isRegistered())
				failures.put(null, new Failure(GwtbbSmess.instance().userSuspended()));
			if (failures.isEmpty()) {
				user = cloneSansPassword(dbUser);
				sessionId = doLogin(dbUser);
				// no need to send back an empty map
				failures = null;
			}
		}
		retVal.setFailures(failures);
		retVal.setSessionId(sessionId);
		retVal.setUser(user);
		return retVal;
	}

	@Transactional
	public RegisterResponse register(RegisterUserBean registerUserBean) {
		if (registerUserBean == null)
			return null;

		RegisterResponse retVal = new RegisterResponse();
		Map<FieldId, Failure> failures = registerUserBean.validate(GwtbbValidator.newMap(), 0);
		String sessionId = null;
		User user = null;
		if (failures.isEmpty()) {
			if (userDao.selectByUsername(registerUserBean.getUsername()) != null)
				failures.put(FieldId.create(UserBase.USERNAME, 0), new Failure(GwtbbSmess.instance().alreadyRegistered()));
			if (failures.isEmpty()) {
				User dbUser = new User();
				ObjectUtils.copyProperties(dbUser, registerUserBean);
				dbUser.setPrivilegeLevel(User.PrivilegeLevel.UNVERIFIED);
				dbUser.setSnapDate(new Date());

				if (log.isDebugEnabled())
					log.debug("registering new user: " + dbUser.getUsername());

				userDao.create(dbUser);
				UserSnap userSnap = new UserSnap();
				ObjectUtils.copyProperties(userSnap, dbUser, UserBase.PROPERTIES);
				userSnap.setActorId(dbUser.getId());
				userSnap.setProcess(ProcessNames.CREATE_USER);
				userSnapDao.create(userSnap);

				// we make a separate user to blot out the password
				user = cloneSansPassword(dbUser);
				sessionId = doLogin(dbUser);

				// no need to send back an empty map
				failures = null;
			}
		}
		retVal.setFailures(failures);
		retVal.setSessionId(sessionId);
		retVal.setUser(user);
		return retVal;
	}

	private static User cloneSansPassword(User user) {
		User retVal = new User();
		ObjectUtils.copyProperties(retVal, user, User.PROPERTIES);
		retVal.setPassword(null);
		return retVal;
	}

	private String doLogin(User user) {
		return getSessionId(user);
	}

	private String getSessionId(User user) {
		// todo add cacheing
		StringBuffer sb = new StringBuffer();
		char[] name = user.getUsername().toCharArray();
		char[] pass = user.getPassword().toCharArray();
		long id = user.getId();
		for (int i = 0; i < 12; i++) {
			char mask = (char)(((id * 1023) >> i) ^ pass[(i + 3) % pass.length]);
			char letter = name[i % name.length];
			letter ^= mask;
			sb.append(Integer.toString((letter + (i * 7)) % 10));
		}
		return sb.toString();
	}
}
